unit MainFrm;

interface

uses
  ColorSchemes, EditFra, System.Generics.Collections, IniFiles,
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, System.Actions, Vcl.ActnList,
  System.ImageList, Vcl.ImgList, PngImageList, Vcl.ComCtrls, Vcl.ToolWin,
  Vcl.StdActns, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TMainForm = class(TForm)
    MainMenu1: TMainMenu;
    ActionList1: TActionList;
    ActionImages: TPngImageList;
    FileClose: TAction;
    FileCloseAll: TAction;
    FileCloseOthers: TAction;
    FileCloseRight: TAction;
    FileReload: TAction;
    FileExit: TAction;
    FileMenu: TMenuItem;
    EditMenu: TMenuItem;
    Open1: TMenuItem;
    Reload1: TMenuItem;
    N1: TMenuItem;
    Close1: TMenuItem;
    CloseAll1: TMenuItem;
    N2: TMenuItem;
    Exit1: TMenuItem;
    RecentMenu: TMenuItem;
    N3: TMenuItem;
    EditCopy: TAction;
    EditSelAll: TAction;
    EditSelInv: TAction;
    EditEncoding: TAction;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    RecentDropdown: TPopupMenu;
    ViewMenu: TMenuItem;
    Copy1: TMenuItem;
    SelectAll1: TMenuItem;
    InvertSelection1: TMenuItem;
    Encoding1: TMenuItem;
    N5: TMenuItem;
    EditFind: TAction;
    EditFindNext: TAction;
    ViewBkmkToggle: TAction;
    ViewBkmkClear: TAction;
    Find1: TMenuItem;
    FindNext1: TMenuItem;
    oogleBookmark1: TMenuItem;
    ToolButton2: TToolButton;
    ToolButton3: TToolButton;
    GotoBookmark1: TMenuItem;
    ViewBkmkBack: TAction;
    ViewBkmkNext: TAction;
    BkmkPrev1: TMenuItem;
    BkmkNext1: TMenuItem;
    N7: TMenuItem;
    ClearBookmarks1: TMenuItem;
    ToolButton4: TToolButton;
    ToolButton5: TToolButton;
    ToolButton6: TToolButton;
    ToolButton7: TToolButton;
    NavDropdown: TPopupMenu;
    ViewNavBack: TAction;
    ViewNavNext: TAction;
    ToolButton8: TToolButton;
    ToolButton10: TToolButton;
    ToolButton11: TToolButton;
    PageContainer: TPageControl;
    FileOpen: TFileOpen;
    ToolButton14: TToolButton;
    SchemeDropdown: TPopupMenu;
    FileReloadAll: TAction;
    L1: TMenuItem;
    N8: TMenuItem;
    ToolButton9: TToolButton;
    EditFindBack: TAction;
    BkmkDropdown: TPopupMenu;
    MenuItem3: TMenuItem;
    ToolButton16: TToolButton;
    ToolButton18: TToolButton;
    Panel1: TPanel;
    ToolButton12: TToolButton;
    FindCombo: TComboBox;
    TabPopup: TPopupMenu;
    EditPopup: TPopupMenu;
    C1: TMenuItem;
    E1: TMenuItem;
    T1: TMenuItem;
    A1: TMenuItem;
    N9: TMenuItem;
    N10: TMenuItem;
    R1: TMenuItem;
    C2: TMenuItem;
    T2: TMenuItem;
    N11: TMenuItem;
    N12: TMenuItem;
    A2: TMenuItem;
    I1: TMenuItem;
    ToolButton17: TToolButton;
    ToolButton19: TToolButton;
    EditSettings: TAction;
    N13: TMenuItem;
    Settings1: TMenuItem;
    N14: TMenuItem;
    FileClearRecent: TAction;
    L2: TMenuItem;
    SchemeMenu: TMenuItem;
    FileRecent: TAction;
    EditScheme: TAction;
    ViewBkmkGoto: TAction;
    ViewNavType: TAction;
    NavTypeMenu: TMenuItem;
    ViewNavGoto: TAction;
    P1: TMenuItem;
    N15: TMenuItem;
    N16: TMenuItem;
    P2: TMenuItem;
    N4: TMenuItem;
    ViewGotoLine: TAction;
    G1: TMenuItem;
    ToolButton20: TToolButton;
    ToolButton13: TToolButton;
    ToolButton15: TToolButton;
    ToolButton21: TToolButton;
    EditCustomizeColor: TAction;
    C4: TMenuItem;
    C5: TMenuItem;
    N6: TMenuItem;
    NavImages: TImageList;
    procedure FileOpenAccept(Sender: TObject);
    procedure DropdownButtonClick(Sender: TObject);
    procedure EditEncodingExecute(Sender: TObject);
    procedure EditSettingsExecute(Sender: TObject);
    procedure FileCloseExecute(Sender: TObject);
    procedure FileCloseUpdate(Sender: TObject);
    procedure FileCloseAllExecute(Sender: TObject);
    procedure FileCloseAllUpdate(Sender: TObject);
    procedure FileCloseRightExecute(Sender: TObject);
    procedure FileCloseRightUpdate(Sender: TObject);
    procedure FileCloseOthersExecute(Sender: TObject);
    procedure FileCloseOthersUpdate(Sender: TObject);
    procedure FileReloadExecute(Sender: TObject);
    procedure FileReloadUpdate(Sender: TObject);
    procedure EditCopyExecute(Sender: TObject);
    procedure EditCopyUpdate(Sender: TObject);
    procedure EditSelAllExecute(Sender: TObject);
    procedure EditSelAllUpdate(Sender: TObject);
    procedure EditSelInvExecute(Sender: TObject);
    procedure EditSelInvUpdate(Sender: TObject);
    procedure EditEncodingUpdate(Sender: TObject);
    procedure FileClearRecentExecute(Sender: TObject);
    procedure FileClearRecentUpdate(Sender: TObject);
    procedure EmptyActionExecute(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure EditSchemeUpdate(Sender: TObject);
    procedure FileReloadAllExecute(Sender: TObject);
    procedure FileReloadAllUpdate(Sender: TObject);
    procedure EditFindExecute(Sender: TObject);
    procedure EditFindUpdate(Sender: TObject);
    procedure EditFindBackExecute(Sender: TObject);
    procedure EditFindBackUpdate(Sender: TObject);
    procedure EditFindNextExecute(Sender: TObject);
    procedure EditFindNextUpdate(Sender: TObject);
    procedure EditCustomizeColorUpdate(Sender: TObject);
    procedure EditCustomizeColorExecute(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ViewNavTypeUpdate(Sender: TObject);
    procedure PageContainerChange(Sender: TObject);
  private
    { Private declarations }
    FCurrentSchemeID: string;

    function ActiveEditor: TEditFrame;
    function OpenFile(const AFileName, ASchemeID: string): TEditFrame;
    procedure CloseTab(ATab: TTabSheet);
    procedure ColorSchemeChanged(Sender: TObject);

    procedure BuildSchemeMenus;
    procedure CheckSchemeMenus;
    procedure SchemeItemClick(Sender: TObject);
    procedure BuildRecentMenus;
    procedure RecentItemClick(Sender: TObject);
    procedure BuildNavMenus;
    procedure CheckNavMenus;
    procedure NavItemClick(Sender: TObject);

    procedure ReadSettings(AIni: TIniFile);
    procedure WriteSettings(AIni: TIniFile);
    procedure ResetSettingStates;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

uses
  MRU, Settings, EncodingFrm, ConfigFrm, Helpers,
  TypInfo, PngImage;

const
  SConfigFile = '.\app.config';
  SSchemeConfigFile = '.\app.schemes';

procedure TMainForm.FormCreate(Sender: TObject);
begin
  var Ini := TIniFile.Create(SConfigFile);
  try
    ReadSettings(Ini);
    AppMRUFiles.LoadFromFile(Ini);
    AppSettings.LoadFromFile(Ini);
    AppColorSchemes.LoadFromFile(SSchemeConfigFile);

    ResetSettingStates;
    BuildSchemeMenus;
    BuildRecentMenus;
  finally
    Ini.Free;
  end;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FileCloseAll.Execute;
  var Ini := TIniFile.Create(SConfigFile);
  try
    WriteSettings(Ini);
    AppMRUFiles.SaveToFile(Ini);
  finally
    Ini.Free;
  end;
end;

procedure TMainForm.ReadSettings(AIni: TIniFile);
begin
  // window
  Top := AIni.ReadInteger('Window', 'Top', Top);
  Left := AIni.ReadInteger('Window', 'Left', Left);
  Width := AIni.ReadInteger('Window', 'Width', Width);
  Height := AIni.ReadInteger('Window', 'Height', Height);
  WindowState := TWindowState(AIni.ReadEnum<TWindowState>('Window', 'State', wsNormal));
end;

procedure TMainForm.WriteSettings(AIni: TIniFile);
begin
  // windows
  AIni.WriteEnum<TWindowState>('Window', 'State', WindowState);
  if WindowState = wsMaximized then WindowState := wsNormal;
  AIni.WriteInteger('Window', 'Top', Top);
  AIni.WriteInteger('Window', 'Left', Left);
  AIni.WriteInteger('Window', 'Width', Width);
  AIni.WriteInteger('Window', 'Height', Height);
end;

procedure TMainForm.ResetSettingStates;
begin
  // reset schemes state
  AppSettings.Modified := False;
  AppColorSchemes.Modified := False;
  for var Scheme in AppColorSchemes do
    TColorScheme(Scheme).Modified := False;
end;

procedure TMainForm.DropdownButtonClick(Sender: TObject);
begin
  TToolButton(Sender).CheckMenuDropdown;
end;

function TMainForm.ActiveEditor: TEditFrame;
begin
  var Tab := PageContainer.ActivePage;
  if Assigned(Tab) then
    Result := TEditFrame(Tab.Controls[0])
  else
    Result := nil;
end;

function TMainForm.OpenFile(const AFileName, ASchemeID: string): TEditFrame;
begin
  Result := nil;
  for var Tab in PageContainer do
    if SameText(TTabSheet(Tab).Hint, AFileName) then
    begin
      Result := TEditFrame(TTabSheet(Tab).Controls[0]);
      Break;
    end;

  if not Assigned(Result) then
  begin
    var Tab := TTabSheet.Create(PageContainer);
    Tab.PageControl := PageContainer;
    Tab.PopupMenu := EditPopup;
    Tab.Caption := ExtractFileName(AFileName);
    Tab.Hint := AFileName;

    var Edit := TEditFrame.Create(Tab);
    Edit.Parent := Tab;
    Edit.Align := alClient;
    Edit.OnColorSchemeChanged := ColorSchemeChanged;
    Edit.ApplySettings;
    Edit.ApplyColorScheme(ASchemeID);
    Edit.LoadFromFile(AFileName);

    Result := Edit;
  end;

  AppMRUFiles.Add(AFileName);
  BuildRecentMenus;

  PageContainer.ActivePage := TTabSheet(Result.Parent);
  PageContainerChange(PageContainer);
end;

procedure TMainForm.CloseTab(ATab: TTabSheet);
begin
  var WaitCursor := Screen.SetWaitCursor;
  with TEditFrame(ATab.Controls[0]) do
    AppMRUFiles.FileDataOf[FileName] := ColorSchemeID;

  ATab.Free;
end;

procedure TMainForm.ColorSchemeChanged(Sender: TObject);
begin
  with TEditFrame(Sender) do
    if Parent = PageContainer.ActivePage then
    begin
      FCurrentSchemeID := '';
      BuildNavMenus;
    end;
end;

procedure TMainForm.PageContainerChange(Sender: TObject);
begin
  BuildNavMenus;
end;

procedure TMainForm.EmptyActionExecute(Sender: TObject);
begin
  //
end;

procedure TMainForm.FileOpenAccept(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  for var FileName in FileOpen.Dialog.Files do
    OpenFile(FileName, AppMRUFiles.FileDataOf[FileName]);
end;

procedure TMainForm.FileReloadExecute(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  ActiveEditor.Reload;
end;

procedure TMainForm.FileReloadUpdate(Sender: TObject);
begin
  FileReload.Enabled := PageContainer.ActivePageIndex >= 0;
end;

procedure TMainForm.FileReloadAllExecute(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  for var I := 0 to PageContainer.PageCount - 1 do
    TEditFrame(PageContainer.Pages[I].Controls[0]).Reload;
end;

procedure TMainForm.FileReloadAllUpdate(Sender: TObject);
begin
  FileReloadAll.Enabled := PageContainer.PageCount > 0;
end;

procedure TMainForm.FileCloseExecute(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  CloseTab(PageContainer.ActivePage);
  PageContainerChange(PageContainer);
end;

procedure TMainForm.FileCloseUpdate(Sender: TObject);
begin
  FileClose.Enabled := PageContainer.ActivePageIndex >= 0;
end;

procedure TMainForm.FileCloseAllExecute(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  for var I := PageContainer.PageCount - 1 downto 0 do
    CloseTab(PageContainer.Pages[I]);
  PageContainerChange(PageContainer);
end;

procedure TMainForm.FileCloseAllUpdate(Sender: TObject);
begin
  FileCloseAll.Enabled := PageContainer.PageCount > 0;
end;

procedure TMainForm.FileCloseRightExecute(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  var Selected := PageContainer.ActivePageIndex;
  for var I := PageContainer.PageCount - 1 downto Selected + 1 do
    CloseTab(PageContainer.Pages[I]);
end;

procedure TMainForm.FileCloseRightUpdate(Sender: TObject);
begin
  FileCloseRight.Enabled := PageContainer.ActivePageIndex < PageContainer.PageCount - 1;
end;

procedure TMainForm.FileCloseOthersExecute(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  var Selected := PageContainer.ActivePageIndex;
  for var I := PageContainer.PageCount - 1 downto 0 do
    if I <> Selected then CloseTab(PageContainer.Pages[I]);
end;

procedure TMainForm.FileCloseOthersUpdate(Sender: TObject);
begin
  FileCloseOthers.Enabled := PageContainer.PageCount > 1;
end;

procedure TMainForm.FileClearRecentExecute(Sender: TObject);
begin
  AppMRUFiles.Clear;
  BuildRecentMenus;
end;

procedure TMainForm.FileClearRecentUpdate(Sender: TObject);
begin
  FileClearRecent.Enabled := AppMRUFiles.FileCount > 0;
end;

procedure TMainForm.EditCopyExecute(Sender: TObject);
begin
  SendMessage(GetFocus, WM_COPY, 0, 0);
end;

function IsEditorFocused(ActiveControl: TControl): Boolean; inline;
begin
  Result := Assigned(ActiveControl) and Assigned(ActiveControl.Parent)
    and (ActiveControl.Parent is TEditFrame);
end;

procedure TMainForm.EditCopyUpdate(Sender: TObject);
begin
  if ActiveControl is TCustomEdit then
  begin
    var E := 0; var S := 0;
    SendMessage(ActiveControl.Handle, EM_GETSEL, Integer(@S), Integer(@E));
    EditCopy.Enabled := E > S;
  end
  else if ActiveControl is TCustomCombo then
  begin
    var E := 0; var S := 0;
    SendMessage(ActiveControl.Handle, CB_GETEDITSEL, Integer(@S), Integer(@E));
    EditCopy.Enabled := E > S;
  end
  else if IsEditorFocused(ActiveControl) then
    EditCopy.Enabled := TEditFrame(ActiveControl.Parent).CanCopy
  else
    EditCopy.Enabled := False;
end;

procedure TMainForm.EditSelAllExecute(Sender: TObject);
begin
  if (ActiveControl is TCustomEdit) or (ActiveControl is TCustomCombo) then
    SendMessage(GetFocus, EM_SETSEL, 0, -1)
  else
    ActiveEditor.SelectAll;
end;

procedure TMainForm.EditSelAllUpdate(Sender: TObject);
begin
  if (ActiveControl is TCustomEdit) or (ActiveControl is TCustomCombo) then
    EditSelAll.Enabled := ActiveControl.Enabled and (SendMessage(GetFocus, WM_GETTEXTLENGTH, 0, 0) > 0)
  else if IsEditorFocused(ActiveControl) then
    EditSelAll.Enabled := TEditFrame(ActiveControl.Parent).CanSelectAll
  else
    EditSelAll.Enabled := False;
end;

procedure TMainForm.EditSelInvExecute(Sender: TObject);
begin
  ActiveEditor.InvertSelection;
end;

procedure TMainForm.EditSelInvUpdate(Sender: TObject);
begin
  EditSelInv.Enabled := IsEditorFocused(ActiveControl) and ActiveEditor.CanSelectAll;
end;

procedure TMainForm.EditSchemeUpdate(Sender: TObject);
begin
  EditScheme.Enabled := PageContainer.ActivePageIndex >= 0;
  if EditScheme.Enabled then CheckSchemeMenus;
end;

procedure TMainForm.EditEncodingExecute(Sender: TObject);
var
  NewEncoding: TEncoding;
begin
  NewEncoding := ActiveEditor.Encoding;
  if not TEncodingForm.SelectEncoding(Self, NewEncoding) then Exit;

  var WaitCursor := Screen.SetWaitCursor;
  ActiveEditor.Reload(NewEncoding);
end;

procedure TMainForm.EditEncodingUpdate(Sender: TObject);
begin
  EditEncoding.Enabled := PageContainer.ActivePageIndex >= 0;
end;

procedure TMainForm.EditSettingsExecute(Sender: TObject);
begin
  if not TConfigForm.Configuration(Self) then Exit;
  var WaitCursor := Screen.SetWaitCursor;

  // apply to each editor
  for var Tab in PageContainer do
    with TEditFrame(TWinControl(Tab).Controls[0]) do
    begin
      ApplyColorScheme(ColorSchemeID);
      if AppSettings.Modified then ApplySettings;
    end;
  // save and rebuild menus
  if AppSettings.Modified then
  begin
    AppMRUFiles.MaxCount := AppSettings.FileMaxCount;
    AppSettings.SaveToFile(SConfigFile);
    BuildRecentMenus;
  end;
  if AppColorSchemes.Modified then
  begin
    AppColorSchemes.SaveToFile(SSchemeConfigFile);
    BuildSchemeMenus;
  end;

  // reset states
  ResetSettingStates;
end;

procedure TMainForm.EditCustomizeColorExecute(Sender: TObject);
begin
  var ID := ActiveEditor.ColorSchemeID;
  if not TConfigForm.Configuration(Self, ID) then Exit;
  var WaitCursor := Screen.SetWaitCursor;

  var Scheme := AppColorSchemes.Find(ID);
  if not Scheme.Modified then Exit;

  // apply to referenced editors
  for var Tab in PageContainer do
    with TEditFrame(TWinControl(Tab).Controls[0]) do
      if ColorSchemeID = ID then ApplyColorScheme(ID);

  // reset schemes state
  Scheme.Modified := False;
  AppColorSchemes.Modified := False;
end;

procedure TMainForm.EditCustomizeColorUpdate(Sender: TObject);
begin
  EditCustomizeColor.Enabled := (PageContainer.ActivePageIndex >= 0)
    and not ActiveEditor.ColorSchemeID.IsEmpty;
end;

procedure TMainForm.EditFindExecute(Sender: TObject);
begin
  //
end;

procedure TMainForm.EditFindUpdate(Sender: TObject);
begin
  EditFind.Enabled := PageContainer.ActivePageIndex >= 0;
end;

procedure TMainForm.EditFindBackExecute(Sender: TObject);
begin
  //
end;

procedure TMainForm.EditFindBackUpdate(Sender: TObject);
begin
  EditFindBack.Enabled := PageContainer.ActivePageIndex >= 0;
end;

procedure TMainForm.EditFindNextExecute(Sender: TObject);
begin
   //
end;

procedure TMainForm.EditFindNextUpdate(Sender: TObject);
begin
  EditFindNext.Enabled := PageContainer.ActivePageIndex >= 0;
  FindCombo.Enabled := PageContainer.ActivePageIndex >= 0;
end;

procedure TMainForm.ViewNavTypeUpdate(Sender: TObject);
begin
  ViewNavType.Enabled := NavDropdown.Items.Count > 0;
  ViewNavType.ImageIndex := ViewNavType.Tag - Integer(not ViewNavType.Enabled);
  if ViewNavType.Enabled then CheckNavMenus;
end;

procedure TMainForm.BuildRecentMenus;
var
  List: TStrings;
  procedure BuildRecentMenu(AItem: TMenuItem);
  begin
    while AItem.Count > AItem.Tag do AItem.Delete(0);
    for var I := 0 to List.Count - 1 do
    begin
      var NewItem := TMenuItem.Create(AItem);
      var FileName := List.KeyNames[I];
      NewItem.Caption := MenuPrefixString(I, 'L', ' ')
        + ShortenFileName(FileName, AppSettings.FileMaxWidth);
      NewItem.Hint := List.ValueFromIndex[I];
      NewItem.Tag := I;
      NewItem.OnClick := RecentItemClick;
      AItem.Insert(I, NewItem);
    end;
  end;
begin
  List := TStringList.Create;
  try
    AppMRUFiles.ToList(List);
    BuildRecentMenu(RecentMenu);
    BuildRecentMenu(RecentDropDown.Items);
  finally
    List.Free;
  end;
end;

procedure TMainForm.RecentItemClick(Sender: TObject);
begin
  var WaitCursor := Screen.SetWaitCursor;
  with TMenuItem(Sender) do
  begin
    var FileName := AppMRUFiles[Tag];
    OpenFile(FileName, Hint);
  end;
end;

procedure TMainForm.BuildSchemeMenus;
  procedure BuildSchemeMenu(AItem: TMenuItem);
  var
    NewItem: TMenuItem;
  begin
    while AItem.Count > AItem.Tag do AItem.Delete(0);

    NewItem := TMenuItem.Create(AItem);
    NewItem.Caption := 'None';
    NewItem.Tag := -1;
    NewItem.OnClick := SchemeItemClick;
    AItem.Insert(0, NewItem);

    NewItem := TMenuItem.Create(AItem);
    NewItem.Caption := '-';
    AItem.Insert(1, NewItem);

    for var I := 0 to AppColorSchemes.Count - 1 do
    begin
      var Scheme := AppColorSchemes[I];
      NewItem := TMenuItem.Create(AItem);
      NewItem.Caption := MenuPrefixString(I, 'C', ' ') + Scheme.DisplayName;
      NewItem.Hint := Scheme.DisplayName;
      NewItem.Tag := I;
      NewItem.OnClick := SchemeItemClick;
      AItem.Insert(I + 2, NewItem);
    end;
  end;
begin
  BuildSchemeMenu(SchemeMenu);
  BuildSchemeMenu(SchemeDropdown.Items);
end;

procedure TMainForm.CheckSchemeMenus;
var
  SelectedID: string;
  procedure CheckSchemeMenu(AItem: TMenuItem);
  begin
    for var I := 0 to AItem.Count - 1 - AItem.Tag do
    begin
      var Item := AItem[I];
      if Item.IsLine then Continue;
      if Item.Tag < 0 then
        Item.Checked := SelectedID.IsEmpty
      else
        Item.Checked := AppColorSchemes[Item.Tag].SchemeID = SelectedID;
    end;
  end;
begin
  SelectedID := ActiveEditor.ColorSchemeID;
  CheckSchemeMenu(SchemeMenu);
  CheckSchemeMenu(SchemeDropdown.Items);
end;

procedure TMainForm.SchemeItemClick(Sender: TObject);
begin
  var Index := TMenuItem(Sender).Tag;
  if Index < 0 then
    ActiveEditor.ApplyColorScheme('')
  else
  begin
    var Scheme := AppColorSchemes[Index];
    ActiveEditor.ApplyColorScheme(Scheme.SchemeID);
  end;
end;

function CreateColorBitmap(Item: TColorItem; ImageSize, IconSize: Integer;
  var TransColor: TColor): TBitmap;
begin
  Result := TBitmap.Create;
  Result.PixelFormat := pf32Bit;
  Result.SetSize(ImageSize, ImageSize);

  var BackColor := clWindow;
  if Item.BackColor <> clDefault then
    BackColor := Item.BackColor;
  var ForeColor := clWindowText;
  if Item.ForeColor <> clDefault then
    ForeColor := Item.ForeColor;

  TransColor := clFuchsia;
  while (TransColor = BackColor) or (TransColor = ForeColor) do
    Inc(TransColor);

  var S: string := 'a';
  var R := Rect(0, 0, ImageSize, ImageSize);
  // bg
  Result.Canvas.Brush.Color := TransColor;
  Result.Canvas.FillRect(R);
  // icon
  Result.Canvas.Brush.Color := BackColor;
  Result.Canvas.Pen.Color := BackColor;
  // text
  Result.Canvas.Font.Color := ForeColor;
  Result.Canvas.Font.Size := IconSize div 2 - 1;
  // draw icon
  var Spacing := (IconSize - ImageSize) div 2;
  InflateRect(R, Spacing, Spacing);
  if Spacing = 0 then
    Result.Canvas.FillRect(R)
  else
    Result.Canvas.RoundRect(R, -Spacing, -Spacing);
  // draw text
  Result.Canvas.TextRect(R, S,
    [tfCenter, tfVerticalCenter, tfSingleLine, tfNoClip, tfNoPrefix]);
end;

procedure TMainForm.BuildNavMenus;
var
  Scheme: TColorScheme;
  procedure BuildSchemeImages;
  var
    TransColor: TColor;
  begin
    NavImages.Clear;
    if not Assigned(Scheme) then Exit;

    for var I := 0 to Scheme.Colors.Count - 1 do
    begin
      var ColorItem := Scheme.Colors[I];
      if ColorItem.Active and not ColorItem.Pattern.IsEmpty then
      begin
        var Bitmap := CreateColorBitmap(ColorItem, NavImages.Width, NavImages.Height, TransColor);
        try
          NavImages.AddMasked(Bitmap, TransColor);
        finally
          Bitmap.Free;
        end;
      end;
    end;
  end;
  procedure BuildNavMenu(AItem: TMenuItem);
  begin
    while AItem.Count > AItem.Tag do AItem.Delete(0);
    if not Assigned(Scheme) then Exit;

    var Index := 0;
    for var I := 0 to Scheme.Colors.Count - 1 do
    begin
      var ColorItem := Scheme.Colors[I];
      if ColorItem.Active and not ColorItem.Pattern.IsEmpty then
      begin
        var NewItem := TMenuItem.Create(AItem);
        NewItem.Caption := MenuPrefixString(I, '', ' ') + ShortenName(ColorItem.Pattern, 10);
        NewItem.Hint := ColorItem.Pattern;
        NewItem.Tag := I;
        NewItem.ImageIndex := Index;
        NewItem.OnClick := NavItemClick;
        AItem.Insert(Index, NewItem);
        Inc(Index);
      end;
    end;
  end;
begin
  var NewSchemeID := '';
  if PageContainer.ActivePageIndex >= 0 then
    NewSchemeID := ActiveEditor.ColorSchemeID;
  if (FCurrentSchemeID <> NewSchemeID) or
    (FCurrentSchemeID.IsEmpty and (NavImages.Count > 0)) then
  begin
    FCurrentSchemeID := NewSchemeID;
    Scheme := AppColorSchemes.Find(NewSchemeID);
    BuildSchemeImages;
    BuildNavMenu(NavTypeMenu);
    BuildNavMenu(NavDropdown.Items);
  end;

  if PageContainer.ActivePageIndex >= 0 then
  begin
    var Selected := ActiveEditor.NavColorIndex;
    if Selected < 0 then Selected := 0;
    if Selected < NavDropdown.Items.Count then
      NavDropdown.Items[Selected].Click;
  end;
end;

procedure TMainForm.CheckNavMenus;
var
  SelectedIndex: Integer;
  procedure CheckNavMenu(AItem: TMenuItem);
  begin
    for var I := 0 to AItem.Count - 1 - AItem.Tag do
    begin
      var Item := AItem[I];
      if Item.IsLine then Continue;
      Item.Checked := Item.Tag = SelectedIndex;
    end;
  end;
begin
  SelectedIndex := ActiveEditor.NavColorIndex;
  CheckNavMenu(NavTypeMenu);
  CheckNavMenu(NavDropdown.Items);
end;

procedure TMainForm.NavItemClick(Sender: TObject);
var
  TransColor: TColor;
begin
  ActiveEditor.ApplyNavColorIndex(TMenuItem(Sender).Tag);

  var Scheme := AppColorSchemes.Find(ActiveEditor.ColorSchemeID);
  var ColorItem := Scheme.Colors[TMenuItem(Sender).ImageIndex];
  var Bitmap := CreateColorBitmap(ColorItem, 32, 24, TransColor);
  try
    with ViewNavType do
      TPngImageList(Images).ReplaceMasked(Tag, Bitmap, TransColor);
  finally
    Bitmap.Free;
  end;
end;

end.
